package main

import (
	"fmt"
	"log"

	"github.com/valyala/fasthttp"

	"crypto/rand"
	"encoding/base64"
	"time"
)

// CAS 2.0服务结构体
type CASServer struct {
	users      map[string]string // 用户名-密码映射
	sessions   map[string]string // 票据-用户名映射
	sessionTTL time.Duration     // 票据有效期
}

// 创建新的CAS服务器实例
func NewCASServer() *CASServer {
	return &CASServer{
		users:      make(map[string]string),
		sessions:   make(map[string]string),
		sessionTTL: 5 * time.Minute, // 票据默认5分钟有效期
	}
}

// 添加测试用户
func (s *CASServer) AddTestUser(username, password string) {
	s.users[username] = password
}

// 生成随机票据
func (s *CASServer) generateTicket() string {
	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err != nil {
		return "error generating ticket"
	}
	return "ST-" + base64.URLEncoding.EncodeToString(b)
}

// 清理过期会话
func (s *CASServer) cleanupExpiredSessions() {
	// 实际实现中应该有过期时间跟踪
	// 这里简化处理
}

// 处理登录请求
func (s *CASServer) handleLogin(ctx *fasthttp.RequestCtx) {
	service := string(ctx.QueryArgs().Peek("service"))
	// 如果是POST请求，从表单中获取service
	if ctx.IsPost() {
		service = string(ctx.PostArgs().Peek("service"))
	}

	// 检查请求方法
	if ctx.IsPost() {
		// 处理表单提交
		username := string(ctx.PostArgs().Peek("username"))
		password := string(ctx.PostArgs().Peek("password"))

		// 验证用户
		if storedPassword, ok := s.users[username]; ok && storedPassword == password {
			// 生成票据
			ticket := s.generateTicket()
			s.sessions[ticket] = username

			// 重定向回服务，并带上票据
			if service != "" {
				ctx.Redirect(service+"?ticket="+ticket, fasthttp.StatusFound)
				return
			}
			ctx.WriteString("Login successful. Ticket: " + ticket)
		} else {
			ctx.WriteString("Invalid username or password")
		}
	} else {
		// 显示登录表单
		ctx.Response.Header.Set("Content-Type", "text/html")
		// 格式化表单，嵌入service参数
		form := fmt.Sprintf(`<!DOCTYPE html>
<html>
<head>
	<title>CAS 2.0 Login</title>
</head>
<body>
	<h1>CAS 2.0 Login</h1>
	<form method="post">
		<input type="hidden" name="service" value="%s">
		<div>
			<label>Username:</label>
			<input type="text" name="username" required>
		</div>
		<div>
			<label>Password:</label>
			<input type="password" name="password" required>
		</div>
		<div>
			<button type="submit">Login</button>
		</div>
	</form>
</body>
</html>`, service)
		ctx.WriteString(form)
	}
}

// 处理验证票据请求
func (s *CASServer) handleValidate(ctx *fasthttp.RequestCtx) {
	ticket := string(ctx.QueryArgs().Peek("ticket"))
	// service := string(ctx.QueryArgs().Peek("service")) // 目前未使用

	// 清理过期会话
	s.cleanupExpiredSessions()

	// 检查票据是否有效
	username, valid := s.sessions[ticket]

	ctx.Response.Header.Set("Content-Type", "text/xml")
	if valid {
		// 票据有效，返回用户信息
		// 验证成功后可以选择删除票据（一次性使用）
		// delete(s.sessions, ticket)

		ctx.WriteString(fmt.Sprintf(`<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<cas:serviceResponse xmlns:cas=\"http://www.yale.edu/tp/cas\">
  <cas:authenticationSuccess>
    <cas:user>%s</cas:user>
  </cas:authenticationSuccess>
</cas:serviceResponse>`, username))
	} else {
		// 票据无效
		ctx.WriteString(fmt.Sprintf(`<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<cas:serviceResponse xmlns:cas=\"http://www.yale.edu/tp/cas\">
  <cas:authenticationFailure code=\"INVALID_TICKET\">
    Ticket %s not recognized
  </cas:authenticationFailure>
</cas:serviceResponse>`, ticket))
	}
}

// 处理登出请求
func (s *CASServer) handleLogout(ctx *fasthttp.RequestCtx) {
	ticket := string(ctx.QueryArgs().Peek("ticket"))
	if ticket != "" {
		// 删除票据
		delete(s.sessions, ticket)
	}

	// 重定向到登录页
	ctx.Redirect("/login", fasthttp.StatusFound)
}

// 处理首页请求
func (s *CASServer) handleHome(ctx *fasthttp.RequestCtx) {
	ctx.Response.Header.Set("Content-Type", "text/html")
	home := `<!DOCTYPE html>
<html>
<head>
    <title>CAS 2.0 Server Demo</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .container { max-width: 800px; margin: 0 auto; }
        .endpoint { background: #f5f5f5; padding: 15px; margin: 10px 0; border-radius: 5px; }
        .endpoint h3 { margin-top: 0; }
        .endpoint code { background: #e8e8e8; padding: 2px 5px; border-radius: 3px; }
        .test-users { background: #e8f4f8; padding: 15px; margin: 15px 0; border-radius: 5px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>CAS 2.0 Server Demo</h1>
        <p>这是一个使用Go语言和fasthttp实现的CAS 2.0协议单点登录服务演示。</p>
        
        <div class="test-users">
            <h3>测试用户</h3>
            <ul>
                <li><strong>admin</strong> / password</li>
                <li><strong>user1</strong> / 123456</li>
            </ul>
        </div>

        <h2>可用端点</h2>
        
        <div class="endpoint">
            <h3>🔐 登录页面</h3>
            <p><code>/login?service=http://example.com</code></p>
            <p>用户登录页面，登录成功后会重定向到service参数指定的URL并附带ticket</p>
            <a href="/login?service=http://example.com">测试登录</a>
        </div>

        <div class="endpoint">
            <h3>✅ 票据验证</h3>
            <p><code>/validate?ticket=ST-xxx&service=http://example.com</code></p>
            <p>验证票据有效性，返回XML格式的验证结果</p>
        </div>

        <div class="endpoint">
            <h3>🚪 登出</h3>
            <p><code>/logout?ticket=ST-xxx</code></p>
            <p>登出用户，清除会话</p>
        </div>

        <h2>使用示例</h2>
        <ol>
            <li>访问 <a href="/login?service=http://example.com">登录页面</a> 并登录</li>
            <li>登录成功后会重定向到 <code>http://example.com?ticket=ST-xxx</code></li>
            <li>使用返回的ticket调用验证接口验证用户身份</li>
        </ol>
    </div>
</body>
</html>`
	ctx.WriteString(home)
}

// 启动服务器
func (s *CASServer) Start(addr string) error {
	m := func(ctx *fasthttp.RequestCtx) {
		switch string(ctx.Path()) {
		case "/":
			s.handleHome(ctx)
		case "/login":
			s.handleLogin(ctx)
		case "/validate":
			s.handleValidate(ctx)
		case "/logout":
			s.handleLogout(ctx)
		default:
			ctx.Error("Not found", fasthttp.StatusNotFound)
		}
	}

	return fasthttp.ListenAndServe(addr, m)
}

func main() {
	server := NewCASServer()
	// 添加测试用户
	server.AddTestUser("admin", "password")
	server.AddTestUser("user1", "123456")

	log.Println("Starting CAS 2.0 server on :8080...")
	log.Println("Test users: admin/password, user1/123456")
	if err := server.Start(":8080"); err != nil {
		log.Fatalf("Error starting server: %v", err)
	}
}
